<?php
namespace ImWorker;

/**
 * http服务器
 */
class HttpServer
{
    private $swHs;
    private $config;
    private $swConfig;
    private $routeMaps;
    private $argv  = [];
    private $index = ['index.html', 'index.htm'];
    private $method;
    private $restfull = ['get', 'post', 'put', 'patch', 'delete'];

    public function __construct(Config $config)
    {
        $this->config   = $config;
        $this->swConfig = [
            'worker_num'        => 1,
            'max_request'       => 100,
            'open_cpu_affinity' => 1,
            'dispatch_mode'     => 1,
            // 'chroot'            => ROOTPATH,
            'user'              => 'www-data',
            'group'             => 'www-data',
        ];

        $this->routeMaps = [
            '(get|post|put|patch|delete)?'       => ['any', '@App\Controllers\DefaultController']
        ];
    }

    public function init()
    {
        $host = $this->config->get('http.serverHost', '0.0.0.0:9501');
        $port = 9501;
        if (strpos($host, ':') !== false) {
            list($host, $port) = explode(':', $host);
        }

        $this->swConfig  = array_merge($this->swConfig, $this->config->get('http.server', []));
        $this->routeMaps = array_merge($this->routeMaps, $this->config->get('http.route', []));
        $this->index     = array_merge($this->index, $this->config->get('http.index', []));
        $this->swHs      = new \swoole_http_server($host, $port);
    }

    public function handle()
    {
        $this->init();
        $this->swHs->set($this->swConfig);
        $this->swHs->on('Request', [$this, 'onRequest']);
        $this->swHs->on('ManagerStart', [$this, 'onManagerStart']);
        $this->swHs->start();
    }

    public function onManagerStart(\swoole_server $server)
    {
        global $argv;
        \swoole_set_process_name("php {$argv[0]} event worker");
    }

    public function onRequest(\swoole_http_request $request, \swoole_http_response $response)
    {
        $uri          = $request->server['request_uri'];
        $this->method = $request->server['request_method'];
        if ($result = $this->doStatic($uri)) {
            $response->end($result);
            return;
        }

        $callback = $this->route($uri);
        $result   = ['<h1>404 Not Found.</h1>', 404];
        try {
            if ($callback !== false) {
                if (is_callable($callback)) {
                    $result = (array) call_user_func_array($callback, [$request, $response, $this->argv]);
                } elseif (($pos = strpos($callback, '@')) !== false) {
                    $controller = trim($callback, '@');
                    if (!$pos) {
                        if (!empty($this->argv)
                            && in_array($tmp = strtolower(array_shift($this->argv)), $this->restfull)) {
                            $this->method = $tmp;
                        }
                        $action = strtolower($this->method) . 'Action';
                    } else {
                        list($controller, $action) = explode('@', $controller);
                    }

                    if (class_exists($controller)) {
                        $obj = new $controller($request, $response);
                        if (method_exists($obj, $action)) {
                            $result = (array) call_user_func_array([$obj, $action], $this->argv);
                        }

                        if (method_exists($obj, 'clear')) {
                            call_user_func([$obj, $action]);
                        }
                    }
                }
                /** @中断输出 */
                if ($result[0] == '__end__') {
                    return;
                }

                $result[1] = (isset($result[1]) && intval($result[1])) ? $result[1] : 200;
            }
        } catch (\Exception $e) {
            $response->status(500);
            $response->end("<h1>" . $e->getMessage() . "</h1>");
            return;
        }

        $response->status($result[1]);
        $response->end($result[0]);
        return;
    }

    private function doStatic($uri)
    {
        $staticFile = $this->config->get('http.appPath', ROOTPATH) . DS . 'static' . $uri;
        if ('/' == $uri) {
            foreach ($this->index as $value) {
                $filename = $staticFile . $value;
                if (is_file($filename)) {
                    return file_get_contents($filename);
                }
            }
        } elseif (file_exists($staticFile)) {
            return file_get_contents($staticFile);
        }

        return false;
    }

    private function route($uri)
    {
        if (isset($this->routeMaps[$uri])) {
            return $this->parseMethod($this->routeMaps[$uri]);
        }
        foreach ($this->routeMaps as $key => $value) {
            if (preg_match('@^/?' . $key . '/?$@i', $uri, $argv)) {
                $result                 = $this->parseMethod($value);
                !$result or $this->argv = array_slice($argv, 1);
                return $result;
            }
        }

        return false;
    }

    private function parseMethod($route)
    {
        if (empty($route)) {
            return false;
        }
        $route = (array) $route;

        $arrMethod = ['ANY' => true, '*' => true, $this->method => true];
        if (1 == count($route)) {
            array_unshift($route, 'GET');
        }

        list($strMethod, $callback) = $route;
        if (strpos($strMethod, ',') !== false) {
            $strMethod = explode(',', $strMethod);
        }
        foreach ((array) $strMethod as $meth) {
            if (isset($arrMethod[strtoupper($meth)])) {
                return $callback;
            }
        }
        return false;
    }
}
